1-2 创建用户:用户数据简单校验
本节实现注册接口的核心逻辑——创建用户,并引入服务端参数校验的思路,通过 HttpException 对非法请求进行拦截。最后引出 NestJS 管道(Pipe) 的概念,为后续的装饰器校验方案做铺垫。
实现注册接口:创建用户
注册接口与登录接口结构类似,区别在于调用 UserRepository 的 create 方法将新用户写入数据库。
// auth/auth.service.ts
import { Injectable } from '@nestjs/common';
import { UserRepository } from '../user/user.repository';
@Injectable()
export class AuthService {
constructor(private readonly userRepository: UserRepository) {}
async signIn(username: string, password: string) {
const user = await this.userRepository.findByUsername(username);
return user;
}
async signUp(username: string, password: string) {
// 调用 UserRepository 创建用户
const user = await this.userRepository.create({
username,
password,
});
return user;
}
}
typescript
对应的 Repository 实现(Prisma 版本):
// user/user.repository.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UserRepository {
constructor(private readonly prisma: PrismaService) {}
async findByUsername(username: string) {
return this.prisma.user.findUnique({ where: { username } });
}
async create(data: { username: string; password: string }) {
return this.prisma.user.create({ data });
}
}
typescript
测试验证:
POST {{baseUrl}}/auth/signup
Content-Type: application/json
{
"username": "tom",
"password": "mock"
}
text
请求成功后,数据库中会新增一条用户记录。
问题:缺少参数时的错误响应不友好
当客户端不传 username 或 password 时,Prisma 会抛出 PrismaClientValidationError,错误信息晦涩难懂,无法帮助用户定位问题:
{
"error": "PrismaClientValidationError",
"message": "..."
}
json
这种错误暴露了底层数据库细节,对前端开发者或终端用户没有任何帮助。
方案一:在 Controller 层手动校验参数
最直接的做法是在 Controller 中对请求参数进行前置判断,校验不通过则抛出 HttpException:
// auth/auth.controller.ts
import { Controller, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('signin')
signIn(@Body() dto: { username: string; password: string }) {
return this.authService.signIn(dto.username, dto.password);
}
@Post('signup')
signUp(@Body() dto: { username: string; password: string }) {
// 参数非空校验
if (!dto.username || !dto.password) {
throw new HttpException('用户名密码不得为空', HttpStatus.BAD_REQUEST);
}
// 用户名长度校验
if (dto.username.length < 6 || dto.username.length > 16) {
throw new HttpException(
'用户名长度必须是大于6小于16位的字符串',
HttpStatus.BAD_REQUEST,
);
}
// 密码长度校验
if (dto.password.length < 6 || dto.password.length > 20) {
throw new HttpException(
'密码长度必须是大于6小于20位的字符串',
HttpStatus.BAD_REQUEST,
);
}
return this.authService.signUp(dto.username, dto.password);
}
}
typescript
校验逻辑放在 Controller 层的理由:
- 参数校验是对输入数据格式的判断,不涉及数据库操作
- 在 Controller 层拦截可以尽早阻断非法请求,避免不必要的 Service 和 Repository 调用
- 保持 Service 层专注于业务逻辑,Repository 层专注于数据访问
错误响应示例:
{
"statusCode": 400,
"message": "用户名密码不得为空",
"timestamp": "2026-05-19T10:30:00.000Z",
"path": "/api/v1/auth/signup"
}
json
上述响应结构由全局异常过滤器(Global Exception Filter)统一格式化,包含状态码、错误消息、时间戳和请求路径。
手动校验的局限性
虽然手动校验可以工作,但存在明显问题:
| 问题 | 说明 |
|---|---|
| 代码冗余 | 每个接口都需要重复编写 if-else 判断 |
| 可维护性差 | 校验规则分散在各个 Controller 中,修改时容易遗漏 |
| 扩展困难 | 增加新的校验规则(如正则、邮箱格式)需要改动大量代码 |
| 缺少类型安全 | DTO 没有明确的类型定义,参数结构容易失控 |
NestJS 管道(Pipe):更优雅的校验方案
NestJS 提供了 管道(Pipe) 机制来统一处理数据校验和转换:
| 管道类型 | 用途 | 示例 |
|---|---|---|
| 转换(Transformation) | 将输入数据转换为目标类型 | 将字符串参数 "123" 转为整数 123 |
| 验证(Validation) | 验证输入数据的合法性 | 校验用户名长度、密码格式等 |
// 管道的核心接口
interface PipeTransform<T, R> {
transform(value: T, metadata: ArgumentMetadata): R;
}
typescript
管道在路由处理方法执行之前运行,可以在数据到达 Controller 之前完成校验和转换。如果验证失败,管道直接抛出异常,请求不会进入后续处理流程。
后续章节将介绍如何使用 class-validator + class-transformer 配合 NestJS 内置的 ValidationPipe,实现基于装饰器的声明式参数校验,彻底替代手动 if-else。
本节要点
- 注册接口实现:通过
UserRepository.create()方法将用户数据写入数据库 - 参数校验的必要性:缺少校验会导致底层数据库错误暴露给客户端,影响用户体验和安全性
- 手动校验方案:使用
HttpException在 Controller 层进行前置拦截,适合简单场景 - 校验层位置选择:纯参数格式校验放在 Controller,业务逻辑校验放在 Service,数据访问放在 Repository
- 管道预告:NestJS 管道提供转换和验证两大能力,后续将使用装饰器方式替代手动校验
↑